package com.simen.hellobytes.encrypt;

import com.simen.hellobytes.HexUtil;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;

import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;

/**
 * author: SimenHi
 * create time: 2021-04-07 14:29
 * description:
 */
public class CipherEncryption {

    public static enum Algorithm {
        AES("AES"), DES("DES"),DESede("DESede");
        private String value;

        Algorithm(String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return value;
        }

        public String getValue() {
            return value;
        }
    }

    public static enum Mode {
        CBC("CBC"), ECB("ECB");
        private String value;

        Mode(String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return value;
        }

        public String getValue() {
            return value;
        }
    }

    public static enum PKCS {
        NoPadding("NoPadding"),PKCS5Padding("PKCS5Padding"), PKCS7Padding("PKCS7Padding");
        private String value;

        PKCS(String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return value;
        }

        public String getValue() {
            return value;
        }
    }

    private Algorithm algorithm;
    private Mode mode;
    private PKCS pkcs;
    private Key key;
    private String algorithmMode;
    private IvParameterSpec iv;

    public static CipherEncryption newInstance() {
        return new CipherEncryption();
    }

    public CipherEncryption() {
    }

    public byte[] encrypt(byte[] plainByte) throws NoSuchPaddingException,
            NoSuchAlgorithmException,
            BadPaddingException,
            IllegalBlockSizeException,
            InvalidKeyException, InvalidAlgorithmParameterException {
        return encrypt(plainByte, algorithmMode());
    }

    public byte[] encrypt(byte[] plainByte, String algorithmMode) throws NoSuchPaddingException,
            NoSuchAlgorithmException,
            BadPaddingException,
            IllegalBlockSizeException,
            InvalidKeyException, InvalidAlgorithmParameterException {
        Cipher cipher = Cipher.getInstance(algorithmMode);
        if (iv != null) {
            cipher.init(ENCRYPT_MODE, key, iv);
        } else {
            cipher.init(ENCRYPT_MODE, key);
        }
        return cipher.doFinal(plainByte);
    }

    public byte[] decrypt(byte[] cipherByte) throws NoSuchPaddingException,
            NoSuchAlgorithmException,
            BadPaddingException,
            IllegalBlockSizeException,
            InvalidKeyException, InvalidAlgorithmParameterException {
        return decrypt(cipherByte, algorithmMode());
    }

    public byte[] decrypt(byte[] cipherByte, String algorithmMode) throws NoSuchPaddingException,
            NoSuchAlgorithmException,
            BadPaddingException,
            IllegalBlockSizeException,
            InvalidKeyException, InvalidAlgorithmParameterException {
        Cipher cipher = Cipher.getInstance(algorithmMode);
        if (iv != null) {
            cipher.init(DECRYPT_MODE, key, iv);
        } else {
            cipher.init(DECRYPT_MODE, key);
        }
        return cipher.doFinal(cipherByte);
    }

    private String algorithmMode() {
        return algorithmMode != null ? algorithmMode : (algorithm.getValue() + "/" + mode.getValue() + "/" + pkcs.getValue());
    }

    /**
     * @param length 密钥长度，单位：byte位（1字节为8位）
     *               1，DES算法密钥长度为64位（8字节），其中用于运算的长度仅为56位，所以length应该为56.
     *               2，AES算法密钥长度为128（16字节）或192（24字节）或256位（32字节）
     */
    public static SecretKey generateKey(Algorithm algorithm, int length) throws NoSuchAlgorithmException {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm.getValue());
        keyGenerator.init(length);
        SecretKey key = keyGenerator.generateKey();
        return key;
    }

    /**
     * 生成3des加密秘钥
     * @param keyArray 不限长度
     */
    public static Key getRandomKey(CipherEncryption.Algorithm algorithm,byte[] keyArray) throws NoSuchAlgorithmException {
        KeyGenerator kg = KeyGenerator.getInstance(algorithm.getValue());
        kg.init(new SecureRandom(keyArray));
        byte[] encoded=kg.generateKey().getEncoded();
        return new SecretKeySpec(encoded, algorithm.getValue());
    }

    /**
     * @param algorithm
     * @param aesKey 如果为DESede（3des）算法，则aesKey的长度必须为24字节;
     * @return
     */
    public static SecretKey getKey(Algorithm algorithm, byte[] aesKey) {
        return new SecretKeySpec(aesKey, algorithm.getValue());
    }

    public static IvParameterSpec generateIv() {
        byte[] ivArray = new byte[16];
        new SecureRandom().nextBytes(ivArray);
        return generateIv(ivArray);
    }

    public static IvParameterSpec generateIv(byte[] ivArray) {
        return new IvParameterSpec(ivArray);
    }

    public Algorithm getAlgorithm() {
        return algorithm;
    }

    public Mode getMode() {
        return mode;
    }

    public PKCS getPkcs() {
        return pkcs;
    }

    public Key getKey() {
        return key;
    }

    public String getAlgorithmMode() {
        return algorithmMode;
    }

    public IvParameterSpec getIv() {
        return iv;
    }

    public CipherEncryption setAlgorithm(Algorithm algorithm) {
        this.algorithm = algorithm;
        return this;
    }

    public CipherEncryption setMode(Mode mode) {
        this.mode = mode;
        return this;
    }

    public CipherEncryption setPkcs(PKCS pkcs) {
        this.pkcs = pkcs;
        return this;
    }

    public CipherEncryption setKey(Key key) {
        this.key = key;
        return this;
    }

    public CipherEncryption setAlgorithmMode(String algorithmMode) {
        this.algorithmMode = algorithmMode;
        return this;
    }

    public CipherEncryption setIv(IvParameterSpec iv) {
        this.iv = iv;
        return this;
    }
}
